namespace EggOfPanKu.Library.Objects
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Xml;
    using System.Xml.Linq;
    using System.Linq;
    using System.ComponentModel;

    using Library.Infrastructure;

    public abstract class Card : IEquatable<Card>, INotifyPropertyChanged
    {
        #region Fields

        private List<string> _Edition = new List<string>();
        private List<string> _Images = new List<string>();
        private string _ImagePath = "";
        private bool _IsBowed;
        private bool _IsDishonored;
        private bool _IsFlipped;
        private List<string> _Keywords = new List<string>();
        private List<string> _Legal = new List<string>();
        private string _Name;
        private string _Text;
        private string _cardID;
        List<string> _traits = new List<string>();
        List<string> _abilities = new List<string>();
        private int _CountInDeck = 1;

        public List<string> Traits
        {
            get { return _traits; }
            set { _traits = value; }
        }

        public List<string> Abilities
        {
            get { return _abilities; }
            set { _abilities = value; }
        }

        #endregion Fields

        #region Constructors

        public Card(string CardID)
        {
        }

        public Card(XElement cardNode)
        {
            //Set CardID
            CardID = (string)cardNode.Attribute("id");

            //Set CardName
            Name = (string)cardNode.Element("name");

            //Set ImagePath and Edition lists
            var images = cardNode.Elements("image").ToList();
            images.ForEach(c => Images.Add((string)c.Value.Replace("images/cards/", "")));
            images.ForEach(c => Edition.Add((string)c.Attribute("edition")));
            if (images.Count < 1)
                ParseWarning("Images");

            //Set Text and Keyword, not all cards have text, don't log
            var cardText = (string)cardNode.Element("text");
            Text = cardText;
            //Text = cardText.Replace("&#8226;", "*");

            //Do we log missing artists?
            Artist = (string)cardNode.Element("artist");
            
            //Flavor is optional, don't log
            Flavor = (string)cardNode.Element("flavor");

            switch ((string)cardNode.Element("rarity"))
            {
                case "p":
                    Rarity = "Promo";
                    break;

                case "c":
                    Rarity = "Common";
                    break;

                case "u":
                    Rarity = "Uncommon";
                    break;

                case "r":
                    Rarity = "Rare";
                    break;

                case "f":
                    Rarity = "Fixed";
                    break;

                default:
                    Rarity = "Unknown " + Rarity;
                    break;
            }
            if (String.IsNullOrEmpty(Rarity))
                ParseWarning("Rarity");

            //Cards may not have keywords, don't log
            var abilitiesText = cardText;
            Regex keywordRegex = new Regex(@"(?:^<[bB]>([^<:]+)</[bB]>){1}");
            Match keywordMatch = keywordRegex.Match(cardText);
            string keywords = "";
            if (keywordMatch.Success)
            {
                keywords = keywordMatch.Groups[1].Value;
                if (!string.IsNullOrEmpty(keywords))
                {
                    string[] words = (keywords.Replace("&#8226;", "*").Split(new char[] { '*' }, StringSplitOptions.RemoveEmptyEntries));
                    foreach (string word in words)
                    {
                        Keywords.Add(word.Trim());
                    }
                }
                cardText = keywordRegex.Replace(cardText, "", 1).Trim();
            }

            //Cards may not have abilities or traits, don't log
            string[] sep = new string[] { "<br>" };
            string[] abilitiyArray = cardText.Split(sep, StringSplitOptions.RemoveEmptyEntries);

            Regex abilityRegex = new Regex("<b>[\\w\\s]*[(battle)|(limited)|(open)|(reaction)]:</b>.+");
            foreach (string textLine in abilitiyArray)
            {
                Match abilityMatch = abilityRegex.Match(textLine);
                if (abilityMatch.Success)
                {
                    Abilities.Add(textLine);
                }
                else
                {
                    Traits.Add(textLine);
                }
            }


            //Set Legaility
            (cardNode.Elements("legal").ToList()).ForEach(c => Legal.Add(
                CultureInfo.CurrentCulture.TextInfo.ToTitleCase((string)c.Value)));
            if (Legal.Count < 1)
            {
                ParseWarning("Legality");
            }
            if (!Legal.Contains("Open"))
                Legal.Add("Open");
        }

        #endregion Constructors

        #region Properties

        public event PropertyChangedEventHandler PropertyChanged;

        [DataObjectField(true)]
        public string CardID
        {
            get { return _cardID; }
            set { _cardID = value; }
        }

        private string _artist;

        public string Artist
        {
            get { return _artist; }
            set { _artist = value; }
        }
        private string _flavor;

        public string Flavor
        {
            get { return _flavor; }
            set { _flavor = value; }
        }

        private string _rarity;

        public string Rarity
        {
            get { return _rarity; }
            set { _rarity = value; }
        }


        [DataObjectField(true)]
        public string CardTypeName
        {
            get
            {
                return this.CardType.ToString();
            }
        }

        public abstract CardType CardType
        {
            get;
        }

        [DataObjectField(true)]
        public string DeckTypeString
        {
            get
            {
                return this.DeckType.ToString();
            }
        }

        public abstract CardDeck DeckType
        {
            get;
        }

        public List<string> Edition
        {
            get { return _Edition; }
            set { _Edition = value; }
        }

        public List<string> Images
        {
            get { return _Images; }
            set { _Images = value; }
        }

        [DataObjectField(true)]
        public string ImageRelativePath
        {
            get
            {
                return Images[0];
            }
        }
        [DataObjectField(true)]
        public string ImagePath
        {
            get
            {
                //IF there is no imagePath, then initialize the card and find one.
                if (_ImagePath.Length == 0)
                {
                    InitializeImage();
                }
                return _ImagePath;
            }
            set { _ImagePath = value; }
        }

        public bool IsBowed
        {
            get { return _IsBowed; }
            set { _IsBowed = value; }
        }

        public bool IsDishonored
        {
            get { return _IsDishonored; }
            set { _IsDishonored = value; }
        }

        public bool IsDynasty
        {
            get
            {
                return (DeckType == CardDeck.Dynasty);
            }
        }

        public bool IsFate
        {
            get
            {
                return (DeckType == CardDeck.Fate);
            }
        }

        public bool IsFlipped
        {
            get { return _IsFlipped; }
            set { _IsFlipped = value; }
        }

        public bool IsStronghold
        {
            get
            {
                return (DeckType == CardDeck.Stronghold);
            }
        }

        public List<string> Keywords
        {
            get { return _Keywords; }
            set { _Keywords = value; }
        }

        [DataObjectField(true)]
        public string Name
        {
            get { return _Name; }
            set { _Name = value; OnPropertyChanged("Name"); }
        }

        [DataObjectField(true)]
        public string Text
        {
            get { return _Text; }
            set { _Text = value; }
        }

        public List<string> Legal
        {
            get { return _Legal; }
            set { _Legal = value; }
        }

        public int CountInDeck
        {
            get { return _CountInDeck; }
            set { _CountInDeck = value; OnPropertyChanged("CountInDeck"); }
        }

        #endregion Properties

        #region Methods

        public bool IsKeyword(string keyword)
        {
            return Keywords.Contains(keyword);
        }

        public bool IsLegal(string edition)
        {
            return Legal.Contains(edition);
        }

        private void InitializeImage()
        {
            if (this != null)
            {
                //Initialize some things
                bool imageExists = false;

                //Loop through (backwards) the list of associated card images
                for (int i = Images.Count - 1; i > -1; i--)
                {
                    string path = System.IO.Path.Combine(ConfigManager.AppSettingsMgr.CardImagePath, Images[i]);
                    if (System.IO.File.Exists(path))
                    {
                        imageExists = true;
                        _ImagePath = path;
                        break;
                    }
                }

                //IF no image was found, then use a generic one
                if (!imageExists)
                {
                    //Much cleaner than the switch statement
                    _ImagePath = System.IO.Path.Combine(ConfigManager.AppSettingsMgr.ExecutingAssemblyPath, string.Format(@"images\cards\generic_{0}.jpg", this.CardType.ToString().ToLower()));
                }
            }
        }

        protected void ParseWarning(string attribute)
        {
            LogManager.Write(LogManager.Level.WARN,
                String.Format("{0} ({1}) missing {2} value.", Name, CardID, attribute),
                String.Format("ctor:{0}", CardTypeName));
        }

        public bool Equals(Card other)
        {
            return this.CardID.ToUpper().Equals(other.CardID.ToUpper());
        }

        protected void OnPropertyChanged(string info)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(info));
            }
        }

        public override string ToString()
        {
            return Name;
        }

        #endregion Methods


    }

    /// <summary>
    /// Compare cards based on CardID
    /// </summary>
    public class CardComparer : IEqualityComparer<Card>
    {
        public bool Equals(Card x, Card y)
        {
            //Check whether the compared objects reference the same data.
            if (Object.ReferenceEquals(x, y)) return true;

            //Check whether any of the compared objects is null.
            if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
                return false;

            //Check whether the products' properties are equal.
            return x.CardID == y.CardID;
        }

        public int GetHashCode(Card card)
        {
            //Check whether the object is null
            if (Object.ReferenceEquals(card, null)) return 0;

            //Calculate the hash code for the product.
            return card.CardID.GetHashCode();
        }
    }
}